package handler

import (
	"fmt"
	"time"

	"starsine.cn/appTpl/cmn/config/aconf"
	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/svccenter/registry/grpc/scregistryproto"
	"starsine.cn/appTpl/svcregistry/grpc/client"
)

type STATE int

const (
	REGISTRY STATE = iota
	HEARTBEAT_CHECK
)

func (t STATE) String() string {
	switch t {
	case REGISTRY:
		return "registry"
	case HEARTBEAT_CHECK:
		return "heartbeat-check"
	}
	return "unknown-state"
}

type SvcInfo struct {
	SvcType   string
	Name      string
	UserSpace string
	RpcAddr   string
}

func (t SvcInfo) String() string {
	return fmt.Sprintf("SvcInfo: SvcType=%s, Name=%s, UserSpace=%s, RpcAddr=%s", t.SvcType, t.Name, t.UserSpace, t.RpcAddr)
}

type SvcHandler struct {
	SvcInfo

	state      STATE
	instanceID string
	smSwitch   chan STATE
	hbFailCnt  uint32

	appCtxt *AppContext
}

func (t SvcHandler) String() string {
	return fmt.Sprintf("ID: %s, %s", t.instanceID, t.SvcInfo)
}

func CreateSvcHandler(appCtxt *AppContext) *SvcHandler {
	p := new(SvcHandler)
	p.hbFailCnt = 0

	p.smSwitch = make(chan STATE)
	p.state = REGISTRY

	p.instanceID = ""
	p.appCtxt = appCtxt

	p.SvcInfo = SvcInfo{
		SvcType:   aconf.GetString("SvcType", "default", aconf.WithENV("svc_type")),
		Name:      aconf.GetString("NodeName", "default", aconf.WithENV("node_name")),
		UserSpace: aconf.GetString("UserSpace", "default", aconf.WithENV("svc_userspace")),
		RpcAddr:   aconf.GetString("RPC.Addr", "localhost:20001", aconf.WithENV("rpc_addr")),
	}

	return p
}

func (p *SvcHandler) Start() {
	go p.sm()
	p.smSwitch <- REGISTRY
}

func (p *SvcHandler) sm() {
	logs.Infof("%s SvcHandler sm routine start", p.instanceID)
	p.appCtxt.Wg.Add(1)
	defer func() {
		logs.Warnf("%s SvcHandler sm routine exit", p.instanceID)
		p.appCtxt.Wg.Done()
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case s := <-p.smSwitch:
			logs.Debugf("switch to %s", s)
			switch s {
			case REGISTRY:
				ticker.Reset(1 * time.Second)
			case HEARTBEAT_CHECK:
				ticker.Reset(15 * time.Second)
			}
			p.state = s

		case <-ticker.C:
			switch p.state {
			case REGISTRY:
				p.registry()
			case HEARTBEAT_CHECK:
				p.heartbeatCheck()
			}

		case <-p.appCtxt.Ctx.Done():
			logs.Warnf("SvcHandler sm routine ctxt done to quit")
			return
		}
	}
}

func (p *SvcHandler) registry() {
	regInfo, err := client.SvcRegist(&scregistryproto.SvcInfo{
		UserSpace: p.SvcInfo.UserSpace,
		SvcType:   p.SvcInfo.SvcType,
		Name:      p.SvcInfo.Name,
		RpcAddr:   p.SvcInfo.RpcAddr,
	})
	if err != nil {
		logs.Warnf("grpc.SvcHandler registry fail, err: %s", err)
		return
	}

	logs.Debugf("grpc.SvcHandler registry ok")
	p.instanceID = regInfo.InstanceID
	p.hbFailCnt = 0
	go func() {
		p.smSwitch <- HEARTBEAT_CHECK
	}()
}

func (p *SvcHandler) heartbeatCheck() {
	r, err := client.SvcHeartbeat(&scregistryproto.HeartbeatReq{
		InstanceID: p.instanceID,
	})
	if err != nil {
		p.hbFailCnt++
		if p.hbFailCnt > 3 {
			go func() {
				p.smSwitch <- REGISTRY
			}()
		}
		logs.Warnf("%s grpc.SvcHandler heartbeatCheck fail, err: %s", p.instanceID, err)
	} else {
		if !r.Succ {
			p.hbFailCnt++
			if p.hbFailCnt > 3 {
				go func() {
					p.smSwitch <- REGISTRY
				}()
			}
			logs.Warnf("%s grpc.SvcHandler heartbeatCheck fail(succ==false)", p.instanceID)
		} else {
			p.hbFailCnt = 0
			logs.Debugf("%s grpc.SvcHandler heartbeatCheck ok", p.instanceID)
		}
	}

	return
}
